Ruby on Rails 過去のメモ
Gemfileにbootstrapの定義を記述する
code:Gemfile
# bootstrapインストール
gem 'bootstrap', '~> 4.0.0'
gem 'mini_racer'
Dockerにて、docker-compose buildでインストール インストール後は、コンテナを立て直す。docker-compose up -d
app/assets/stylesheets配下にあるファイルの拡張子を.cssから.scssへ変更
中を編集
code:application.scss
# 注意書き、require...の記述を消す
@import "bootstrap";
jsも変更する。
code:application.js
//= require jquery3
//= require popper
//= require bootstrap-sprockets
//= require jquery_ujs
//= require turbolinks
//= require_tree .
Ruby on Rails
基本理念があるらしい
DRY : 同じことを繰り返すな
設定より規約が優先される
MVCアーキテクチャである
適当なwebページを作ってみる
まずはルーティング
code:config/route.rb
Rails.application.routes.draw do
# BoardsControllerのindexメソッドを実行するような指定となっている
root 'boards#index'
end
次にコントローラの作成
app/controllersに作成する。今回はboards_controller.rbとする
名前規則は、小文字_(アンダースコア)controller.rbとすること。=スネークケース
code:app/controllers/boards_controller.rb
# クラス定義
class BoardsController < ApplicationController
# localhost:3000に接続した時に動くindexメソッド
def index
end
end
ビューを作る
app/viewsの下にコントローラ名と同じ名前のディレクトリを作成。
その下に各アクションに対するビューを作成していく。
code:app/views/boards/index.html.erb
<h1>掲示板一覧</h1>
まとめ
ルーティングを書く
コントローラがないならスネークケースでapp/controllerに作る
app/viewsの下にコントローラのアンダースコアの手前のディレクトリを作成
html.erbファイルでビューを作成する
viewを作成する
views/layoutsを開くと、レイアウトビューを確認できる
各ファイルのビューは、<%= yield %>に挿入される
code:html.erb
<!DOCTYPE html>
<html>
<head>
<title>App</title>
<%= csrf_meta_tags %>
<%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
<%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>
</head>
<body>
<%= yield %>
</body>
</html>
scssを新規追加する
Rails/app/assets/stylesheets/boards.scssなど、好きなcssファイルを作る
作った後に、作成したscssをapplication.scssへ書き込みインポートする。
code:application.css
@import "bootstrap";
@import "base";
@import "boards";
Modelを作成する
コマンドで作ろう
docker-compose run web bundle exec rails g model board name:string title:string body:text
docker-compose run web: webのコンテナ内で後ろのコマンドを入力
bundle exec rails g model: migration, modelなどのファイル作成
g : generate
board : 作成するカラム名。boardでboardsテーブルを作成するmigrationファイルができる
name~... : テーブルに作成するカラムとその型指定。
Stringのnameカラム、Stringのtitleカラム、Text型のbodyカラムなど。
code:実行結果
% docker-compose run web bundle exec rails g model board name:string title:string body:text
Creating rails_web_run ... done
Running via Spring preloader in process 27
invoke active_record
create db/migrate/20220112115403_create_boards.rb
create app/models/board.rb
invoke test_unit
create test/models/board_test.rb
create test/fixtures/boards.yml
まだマイグレーションはされていないので、忘れないようにしよう
docker-compose run web bundle exec rake db:migrate
ちなみにロールバックは docker-compose run web bundle exec rake db:rollback
ModelはDBテーブルのレコードを、プログラムのオブジェクトとして扱うことができる
デバッグツールを導入しよう
Gemの一種なので、Gemfileで入れられる
code:Gemfile
# 開発環境でしか必要のないようなgemは、group :developmentへと記述することで取捨選択ができる。
group :development do
# Access an IRB console on exception pages or by using <%= console %> anywhere in the code.
gem 'web-console'
gem 'listen', '~> 3.0.5'
gem 'spring'
gem 'spring-watcher-listen', '~> 2.0.0'
end
docker-compose build => docker-compose up -d
※-dをつけ忘れた時
code:etc
web_1 | => Run rails server -h for more startup options
web_1 | A server is already running. Check /app/tmp/pids/server.pid.
となる。tmp/pids/server.pidを削除してstop -> up -dでやればOK
使ってみる。byebugを使う場合はrailsコンテナにアタッチして使う。
code:etc
% docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
9e981c6a1553 rails_web "bundle exec rails s…" 6 minutes ago Up About a minute 0.0.0.0:3000->3000/tcp rails_web_1
de4a83babfe9 mysql:5.7 "docker-entrypoint.s…" 46 hours ago Up About a minute 3306/tcp, 33060/tcp rails_db_1
% docker attach rails_web_1
コンソールは起動しっぱなしになるので、この状態でコントローラに仕込んでみよう
code:controller.rb
def new
@board = Board.new
binding.pry # 追加
end
コンソール側で応対できる
code:console
Started GET "/boards/new" for 172.20.0.1 at 2022-01-12 12:41:04 +0000
Cannot render console from 172.20.0.1! Allowed networks: 127.0.0.1, ::1, 127.0.0.0/127.255.255.255
ActiveRecord::SchemaMigration Load (3.7ms) SELECT schema_migrations.* FROM schema_migrations
Processing by BoardsController#new as HTML
From: /app/app/controllers/boards_controller.rb:7 BoardsController#new:
4: def new
5: @board = Board.new
6: binding.pry
=> 7: end
1 pry(#<BoardsController>)> # 好きに処理を選ぶことができる 好きに確認できる
code:pry
1 pry(#<BoardsController>)> Board => Board(id: integer, name: string, title: string, body: text, created_at: datetime, updated_at: datetime)
2 pry(#<BoardsController>)> @board 3 pry(#<BoardsController>)> @board.name = 'Konishi' => "Konishi"
処理が終わったらexitすると、viewが表示される
code:pry
4 pry(#<BoardsController>)> exit Rendering boards/new.html.erb within layouts/application
Rendered boards/new.html.erb within layouts/application (55.3ms)
Completed 200 OK in 152171ms (Views: 2263.3ms | ActiveRecord: 2.0ms)
pryで入れた@board.nameがビューに入っている。
https://scrapbox.io/files/61decd1f58957b001d3d851d.png
検証ツールでみると、form method=postになっていて、RESTに沿って作られているのが分かる
attachの抜け方は、ctrlを押してp, qの順で押して抜けられる。
またbinding.pryはテストが終わったら消しておこう
データの作成
code:routes.rb
post 'boards', to: 'boards#create'
code:controller.rb
def create
binding.pry
end
テスト
code:pry
1 pry(#<BoardsController>)> params => <ActionController::Parameters {"utf8"=>"✓", "authenticity_token"=>"6ZHAisWjnYCMbtiHjWLpxsQD1UjxcMwb73HDH8fGJRV1fUVnON4j07faUAw72P1ajnpouzTw1KdtodKdvST93g==", "board"=>{"name"=>"掲示板の名前", "title"=>"タイトルです", "body"=>""}, "commit"=>"保存", "controller"=>"boards", "action"=>"create"} permitted: false>
2 pry(#<BoardsController>)> params:board => <ActionController::Parameters {"name"=>"掲示板の名前", "title"=>"タイトルです", "body"=>""} permitted: false>
上の図だと、commitやcontrollerなども含まれているので、特定のパラムだけ許可するメソッド作成
code:controller.rb
private
def board_params
params.require(:board).permit(:name, :title, :body)
end
code:pry
3 pry(#<BoardsController>)> params => <ActionController::Parameters {"utf8"=>"✓", "authenticity_token"=>"8k7MIGL9OTlIIkhxX1ZayHdiW2J+M+3wH6Vp8+g9lfVuoknNn4CHanOWwPrp7E5UPRvmkbuz9UyddXhxkt9NPg==", "board"=>{"name"=>"掲示板の名前", "title"=>"Railsについて", "body"=>"本文テストです。"}, "commit"=>"保存", "controller"=>"boards", "action"=>"create"} permitted: false>
# 絞り込めている
4 pry(#<BoardsController>)> board_params => <ActionController::Parameters {"name"=>"掲示板の名前", "title"=>"Railsについて", "body"=>"本文テストです。"} permitted: true>
コントローラ編集してテスト
code:controller.rb
def create
Board.create(board_params)
binding.pry
end
code:pry
1 pry(#<BoardsController>)> Board.all => Board Load (0.4ms) SELECT boards.* FROM boards
[#<Board:0x00007fec042034e0
id: 1,
name: "名前です",
title: "タイトルです",
body: "保存済みです。",
created_at: Thu, 13 Jan 2022 11:38:26 UTC +00:00,
updated_at: Thu, 13 Jan 2022 11:38:26 UTC +00:00>]
Board.all.first
保存したデータをindexで開く
code:Ruby
# 全部取得 @はビューで呼び出せる
def index
@boards = Board.all
end
<% @boards.each do |board| %>
<tr>
<th><%= board.id %></th>
<td><%= board.title %></td>
<td><%= board.name %></td>
<td><%= board.created_at %></td>
<td><%= board.updated_at %></td>
</tr>
<% end %>
特定のフォーマットを保存する
日付用のフォーマットなどを作ってみよう
code:ruby
<td><%= board.created_at.strftime('%Y年 %m月 %d日 %H時 %M分') %></td>
↑こういうのを自動化したい
config/initializers/time_formats.rbとかのファイルを作ってみる
code:Ruby
コンテナ再起動で使えるようになる
code:Ruby
<td><%= board.created_at.strftime('%Y年 %m月 %d日 %H時 %M分') %></td>
# フォーマットを使った書き方 to_s(名前)
<td><%= board.updated_at.to_s(:datetime_jp) %></td>
個別の掲示板ページを作る
code:ruby
# /board/1というパスにアクセスがきたら、params:idでURLの1を取得できる。 get 'boards/:id', to: 'boards#show'
4 pry(#<BoardsController>)> params:id => "2"
ちゃんと作ってみる
code:ruby
def show
@board = Board.find(params:id) # binding.pry
end
# view
<p class="card-text">
# 改行も反映される
<%= simple_format(@board.body) %>
</p>
<p class="text-right font-weight-bold mr-10"><%= @board.name %></p>
リソースベースルーティング
railsのルーティングはrails/info/routesにアクセスするとパス一覧が確認できる
https://scrapbox.io/files/61e16ff042d7740021892d6f.png
Helperの部分で記述されているヘルパを使って、その指定されているパスを指定することができる
ルーティングをリソースベースに書き換える
code:routes.rb
Rails.application.routes.draw do
get 'boards', to: 'boards#index'
get 'boards/new', to: 'boards#new'
post 'boards', to: 'boards#create'
get 'boards/:id', to: 'boards#show'
end
# を、↓にする
Rails.application.routes.draw do
# onlyで使うアクションへのルートの指定ができる
end
ヘルパを使ってルート指定する時は、下の感じ
code:erb
<a class="btn btn-outline-dark" href="/boards/new">新規作成</a>
# ↓ページに書かれているヘルパ new_boards_pathを指定
<%= link_to '新規作成', new_board_path, class: 'btn btn-outline-dark' %>
# foreachで回している場合
<td><%= link_to '詳細', board, class: 'btn btn-outline-dark' %></td>
# boardとオブジェクトを指定することで、自動的にそのオブジェクトのリンク先を指定してくれる(すごい)
# board.id=1ならboard/1の方へ飛ばしてくれる
編集画面を作る
RESTfulにおける編集(edit / update)は、GET, PUT/PATCH ルーティングのonlyに追加
コントローラにeditを追加
@board = Board.find(params[:id])
ビューを作る。newを流用しよう
updateメソッドをコントローラに追加
code:controller.rb
def update
board = Board.find(params:id) # モデルオブジェクトの、updateメソッドを使用する
board.update(board_params)
#リダイレクト処理 変数として入れたboardの、/boards/:idにリダイレクトされる redirect_to board
end
newとeditのhtml.erbが似ているので流用する
boardsディレクトリに、_form.html.erbを作成し、似た部分をここに格納する
パーシャルはアンダースコア_からファイル名を開始する
パーシャルを呼び出す
引数に@boardを渡す形にする
<%= render partial: 'form', locals: { board: @board } %>
パーシャル側もこの引数を使う形にする
code:_form.html.erb
# 修正前 @boardというインスタンスしか使えない
<%= form_for @board do |f| %>
# 修正後 renderで渡した引数のインスタンスを持ってくるような形になった
<%= form_for board do |f| %>
パーシャルを呼び出す際は以下でも良い
<%= render partial: 'board', object: @board %>
object: @boardとした場合は、パーシャル名と同じローカル変数が作成されて渡される。
_board.html.erbなら、boardという変数がパーシャル内で使える
モデルのオブジェクトを書く時はさらに簡略できる
<%= render @board %>だけでも良い
削除機能を使う
ルーティングする
resources :boards, only: [:index, :new, :create, :show, :edit, :update, :destroy]
だけど、こう書くならresouces :boardsだけでOK
view
code:index.html.erb
<td><%= link_to '詳細', board, class: 'btn btn-outline-dark' %></td>
# boardオブジェクトを渡すことでboard/:idの指定 + 「method: :delete」でdeleteメソッドを使う
<td><%= link_to '削除', board, class: 'btn btn-outline-dark', method: :delete %></td>
# ↓data-methodという属性が追加される railsではこれを見てデリートすると決めている
https://scrapbox.io/files/61e179f6952f6c001decdb4e.png
コントローラ
code:controller.rb
def destroy
board = Board.find(params:id) board.delete
redirect_to boards_path
end
コントローラ の重複を消す
今回の実装だと、Board.find(params[:id]が色々なところで使われている
リファクタリングしよう
code:boards_controller.rb
class BoardsController < ApplicationController
# onlyで絞り込んだメソッドが呼び出される前に、自動でこのメソッドが呼ばれる
### 中略 ###
def show
# この記述が不要になる
# @board = Board.find(params:id) end
private
def set_target_board
@board = Board.find(params:id) end
end
この他にもメソッド後に実行されるafter_actionや、別のarround_actionなども存在する
シード(seeder)の実装
db/seeds.rbで自動でデータを作る
Rails.envはtest, development, productionの3つの環境がデフォルトで用意されている。
code:ruby
if Rails.env == 'development'
(1..50).each do |i|
Board.create(name: "ユーザー#{i}", title: "タイトル#{i}", body: "本文#{i}")
end
end
docker-compose exec web bundle exec rake db:seedで実行
gem 'kaminari'と追記
docker-compose build
docker-compose up -d
docker-compose exec web bundle exec rails g kaminari:config
ビューファイルを作る
docker-compose exec web bundle exec rails g kaminari:views bootstrap4
code:controller.rb
def index
# デフォルト25件
@boards = Board.page(params:page) end
code:index.html.erb
<%= paginate @boards %>
日本語化
Rails/config/application.rbに、config.i18n.default_locale = :ja
Rails/config/locales/ja.ymlを新規作成
code:ja.yml
ja:
views:
pagination:
first: '最初'
last: '最後'
previous: '前'
next: '次'
truncate: '...'
コンテナ再起動 docker-compose stop => docker-compose up -d
ページネーション部分のhtmlを弄る
上のコマンド(....kaminari:views bootstrap4)じゃできなかったので、
docker-compose exec web rails g kaminari:views defaultやった
普通はできるらしい
色々いじってたら起動しなくなったので、gemfile.lockを消したのちにbuildしなおしてcompose upした
Rails/app/views/kaminari/_paginator.html.erbで手動でbootstrap の整形にしよう
その他ページネーションの設定をいじる場合はconfig/initializers/kaminari_config.rb
ユーザーのセッションに情報を保存して実装されている
次にその値が参照されるまで保存される
code:boards_controller.rb
def create
board = Board.create(board_params)
# フラッシュ変数 リダイレクト先のページに値を渡すことができる
flash:notice = "「#{board.title}」の掲示板を作成しました" redirect_to board
end
code:show.html.erb
<div class="alert alert-primary"><%= flash:notice %></div> <% end %>
redirect_toでフラッシュを設定することもできる
code:boards_controller.rb
def destroy
@board.delete
redirect_to boards_path, flash: { notice: "「#{@board.title}」の掲示板が削除されました"}
end
code:index.html.erb
Modelに保存する前に条件を設定できる
code:app/models/board.rb
class Board < ApplicationRecord
validates :name, presence: true, length: {maximum: 10 }
validates :title, presence: true, length: {maximum: 30 }
validates :body, presence: true, length: {maximum: 100 }
end
presenceが必須であることを示す
自信でバリデーションのカスタムもできる
create部分を編集
code:ruby
def new
@board = Board.new(flash:board) end
def create
# board = Board.create(board_params) これを一度やめて、newで作るというひと段落入れる
board = Board.new(board_params)
if board.save # saveできた時の処理
flash:notice = "「#{board.title}」の掲示板を作成しました" redirect_to board
else # できなかった場合(バリデートされた時)
# createメソッドに飛んでくる前のnewにリダイレクトして、フラッシュメッセージを渡す
redirect_to new_board_path, flash: {
board: board,
error_messages: board.errors.full_messages
}
end
end
ビュー
code:_form.html.erb
<div class="alert alert-danger">
<ul>
<% flash[:error_messages.each dp |msg| %>
<li><%= msg %></li>
</ul>
</div>
<% end %>
表示されるようになるが、英語なので日本語に直す
gem 'rails-i18n'
docker-compose build => docker-compose up -d
一部まだ英語なので、気になるならconfig/locale/ja.ymlを編集
code:ja.yml
ja:
activerecord:
attributes:
board:
name: 名前
title: タイトル
body: 本文
なおymlは""で囲わなくても認識してくれる
Modelのアソシエーション
モデルを紐づけるやり方
例えばboardモデルに、新しくcommentモデルを紐づけるとか。
考え方としては、board_idを持ったcommentsテーブルを作るみたいなのとにてる(リレーション)
boardsとcommentsは1対多のhasmanyの関係を持たせることにする
これはcommentsから見ると、belongs toの設定となる
構造を自動で吐き出してくれるgem、annotationを使おう gem 'annotate'
bulid -> up -d -> docker-compose exec web bundle exec annotate
動かなかったので、まずdocker-compose exec web rails g annotate:install
code:board.rb
## クラスの上部にテーブル構造のコメントが作られる
# == Schema Information
#
# Table name: boards
#
# id :integer not null, primary key
# body :text(65535)
# name :string(255)
# title :string(255)
# created_at :datetime not null
# updated_at :datetime not null
#
class Board < ApplicationRecord
validates :name, presence: true, length: {maximum: 10 }
validates :title, presence: true, length: {maximum: 30 }
validates :body, presence: true, length: {maximum: 100 }
end
毎回コマンドの実行は面倒なので、テーブル作成時に自動でアノテーションが追加されるようにしよう
docker-compose exec web bundle exec rails g annotate:install
commentモデルを作ろう
docker-compose run web bundle exec rails g model comment board:references name:string comment:text
board:references : boardモデルと紐づけるための、board_idカラムが作られる。
外部キー用の書き方
できたmigrationファイル
code:ruby
class CreateComments < ActiveRecord::Migration5.0 def change
create_table :comments do |t|
# boardモデルとの関係, 外部キー制約
t.references :board, foreign_key: true
t.string :name, null: false # not null制約追加
t.text :comment, null: false
t.timestamps
end
end
end
docker-compose exec web bundle exec rake db:migrate
Board,CommentのModelにアソシエーションを設定しよう
code:board.rb
class Board < ApplicationRecord
has_many :comments
code:comment.rb
# board:referencesのコマンドのおかげで、すでにアソシエーションが設定されている
class Comment < ApplicationRecord
belongs_to :board
end
commentコントローラ作り
最初は手作りしたので、次はコマンドで作る
docker-compose exec web rails g controller comments create destroy --skip-template-engine
rails g controller comments create destroy : 名前と作るメソッドを指定
--skip-template-engine : viewの作成をスキップ
code:routes.rb
code:boards_controller.rb
def show # board/1などのURLでコメント記入システムを作る
# 掲示板に紐づいたcommentモデルを作成
@comment = @board.comments.new
end
ビュー
scssにスタイル追記
追記したらapplication.scssに編集することを忘れずに
コメント保存機能の実装
pryのDBデータを整形してくれるgemを入れよう
gem 'rails-flog', require: 'flog'
buildとup -dして整形データ確認できる
入れた値を受け取ってみる
code:comments_controller.rb
def comment_params
# requireで指定し、permitでparamsから受け取るデータを抽出する
params.require(:comment).permit(:board_id, :name, :comment)
end
create側
code:rb
def create
comment = Comment.new(comment_params)
if comment.save # バリデーション問題なく、saveできたら
# コメントに紐づくboardの画面にリダイレクト
# belongs_to設定にしておくことで、board_idに対応するオブジェクトを取得してくれる
redirect_to comment.board
else
# 1つ前の画面に戻し、flashメッセージを作成
redirect_to :back, flash: {
comment: comment,
error_messages: comment.errors.full_messages
}
end
end
コメントを操作するときの流れまとめ
ルートで使うDBのデータを指定。複数形
コントローラ作成。複数形_controller.rb
@comment = Comment.new(board_id: @board.id)
@は単数で指定する。上は、コメントの空素材を作成している。下のフォームに入れるための土台みたいなもの
上がないと、パーシャルで動いている部分が動作しなくなる
<%= render partial: 'comments/form', locals: { comment: @comment } %>
comments/_form.html.erb
https://scrapbox.io/files/61ee81f3df371c001f0d287b.png
undefined method 'comments'のエラー
models部分のvalidateが問題だった
code:rb
validates :comments, presence: true, length: {maximum: 100}
これがcommentのtypoだったのでsaveで止まってしまった カス
詳細なログはlog/development.logでも見れるよ
https://scrapbox.io/files/61ee88a9a2b534001e6fccc4.png
コメント削除
code:rb
def destroy
comment = Comment.find(params:id) comment.delete
# 紐づいていた掲示板画面に遷移させる /board/1の場合はそこ
redirect_to comment.board, flash: { notice: 'コメントが削除されました' }
# ちなみにredirect_to boards_pathはトップに戻る
# redirect_to comment は/commentに戻る(エラー), :backは前のページなど色々ある
# redirect_to @boardはboard/1に戻る = comment.board
end
# view
<%= render partial: 'comments/form', locals: { comment: @comment } %>
# ↓render先。 @commentがcommentという形で渡されている
<span><%= link_to '削除', comment, method: :delete, data: { confirm: '削除してもよろしいですか?' } %>
自分でboard編集時のvalidate, リダイレクト処理を書いた
code:rb
def update
# updateの成功失敗に応じてtrue / falseが返る
is_update = @board.update(board_params)
if is_update == true
redirect_to @board
else
redirect_to :back, flash: {
board: @board, # 掲示板の情報持ち越し
error_messages: @board.errors.full_messages # 受け取っているエラーをフラッシュで送付
}
end
end
多対多の関連付け
掲示板と、タグ付け機能を関連させる
https://scrapbox.io/files/61efd73d196dd70022e53639.png
boardsにはタグが何件もついていていいし、tagsは何件の掲示板に紐づいていても良い
情報を消したいときは、中間テーブルの値を削除すれば良い。
多対多を実装する
model
docker-compose exec web bundle exec rails g model tag name:string
nullは許容しないように修正する
code:rb
class CreateTags < ActiveRecord::Migration5.0 def change
create_table :tags do |t|
t.string :name, null: false
t.timestamps
end
end
end
docker-compose exec web bundle exec rails g model board_tag_relation board:references tag:references
docker-compose exec web bundle exec rake db:migrate
code:Rails/app/models/board_tag_relation.rb
class BoardTagRelation < ApplicationRecord
# referencesをコマンドに含めてmigrateするとこうなる 基本単数形
belongs_to :board
belongs_to :tag
end
code:Rails/app/models/tag.rb
class Tag < ApplicationRecord
# has_manyは複数形
# 追加 1つのtagsが、複数のboard_tag_relationsを持つ (#テストというタグがついている掲示板が5件など)
has_many :board_tag_relations
# through : board_tag_relationsテーブルを経由して、boardsとhas_manyの設定が取らレテいる
has_many :boards, through: :board_tag_relations
end
元々あったboardsモデルもアソシエーションを追記しよう
code:board.rb
class Board < ApplicationRecord
has_many :comments
has_many :board_tag_relations
has_many :tags through: :board_tag_relations
例えば、掲示板を削除したとき
一緒に関連するコメントと、タグ付した中間テーブルの内容も消えてほしい
掲示板のデータを消した時に、それに関する部分も一緒に消えてくれる設定のこと
code:board.rb
class Board < ApplicationRecord
# dependent: :delete_all 該当データが消えた時、こちらのテーブルの関係するものも全部消すという処理
has_many :comments, dependent: :delete_all
has_many :board_tag_relations, dependent: :delete_all
has_many :tags through: :board_tag_relations
なおこちらは、コントローラのdestroyメソッドで動かした時が対象。
code: boards_controller.rb
def destroy
# @board.delete こっちでも消えるけど、dependentを使うために変更
@board.destroy
redirect_to boards_path, flash: { notice: "「#{@board.title}」の掲示板が削除されました"}
end
タグ付機能の実装
軽くseederで実装してみる
code:db/seeds.rb
if Rails.env == 'development'
(1..50).each do |i|
Board.create(name: "ユーザー#{i}", title: "タイトル#{i}", body: "本文#{i}")
end
# 追加
Tag.create([
{ name: 'Ruby' },
{ name: 'Ruby on Rails4' },
{ name: 'Ruby on Rails5' },
{ name: 'Python2' },
{ name: 'Python3' },
{ name: 'Django2' },
])
end
docker-compose exec web rails db:seed
作ったタグをviewに表示する
code:boards/_form.html.erb
<div class="form-group">
<span>タグ</span>
<%#
tagが存在する分だけチェックボックスを作るメソッドのcollenction_check_boxes
boardオブジェクトのプロパティ名tag_ids 多対多の設定によりこれが追加されている。
タグIDのリストを渡すことで複数のタグを紐づけられる
多対多のものを紐づける場合は、:モデル名_idsで紐づけることができる
Tag.all : タグオブジェクトのリスト
:id, :name : ここのtagオブジェクトのid, nameプロパティを記述
%>
<%= f.collection_check_boxes(:tag_ids, Tag.all, :id, :name) do |tag| %>
<div class="form-check">
<%= tag.label class: 'form-check-label' do %>
<%= tag.check_box class: 'form-check-input' %>
<%= tag.text %>
<% end %>
</div>
<% end %>
</div>
controller側も修正
code:boards_controller.rb
def board_params
# 後ろにtag_idsを追加しただけ... 送られてくるparamsに、tag_idsの情報をpermitしてあげるだけ
params.require(:board).permit(:name, :title, :body, tag_ids: [])
end
viewで確認できるよう修正
code:_board.html.erb
<div class="card-header">
<h4><%= board.title %></h4>
# アソシエーションで指定したboard.tagsをforeachで回す
<% board.tags.each do |tag| %>
<span class="badge badge-primary"><%= tag.name %></span>
<% end %>
</div>
タグの掲示板検索
code:rb
<%= form_tag boards_path, method: :get, class: 'boards__searchForm' do %>
<%= select_tag :tag_id,
# option要素を作れるヘルパ :idはvalue, :nameはoptionの表示名を表す
# params:tag_idは検索した後もその値を初期値に設定しておくための設定 options_from_collection_for_select(Tag.all, :id, :name, params:tag_id), {
prompt: 'タグで絞り込み', # 初期値
class: 'form-control boards__select',
onchange: 'submit(this.form)'# 何かを選ぶたびに実行されるjsを動かす
}
%>
<% end %>
indexを書き換える
code:rb
def index
# :tag_idがあった場合は、Tagを:tag_idで検索して、それに紐づくboardsを呼び出している
@boards = params:tag_id.present? ? Tag.find(params:tag_id).boards : Board.all # ここで実際に取得する処理が走る(上ではまだ全件検索していない)
@boards = @boards.page(params:page) end
ヘルパメソッドの作成
viewとかで呼び出せるメソッドのこと
code:view.html.erb
# 実際にはないがこういうの
<%= header_link_item('Home', root_path) %>
定義する場所
code:/Users/skoni/Desktop/RubyLecture/Rails/app/helpers/application_helper.rb
module ApplicationHelper
# リンクの表示名とパスを受け取る
def header_link_item(name, path)
class_name = 'nav-item'
# もし表示するページのパスと、引数のパスが同じだったらclass_にactiveを追加
class_name << 'active' if current_page?(path)
# 任意のhtmliタグを作るヘルパ <li><a></a></li>という形でできる
content_tag :li, class: class_name do
link_to name, path, class: 'nav-link'
end
end
end
一般的な定義場所。特定のコントローラでしか使わないようなものは、別途ファイルを作って定義したほうが良い
いいねボタンでも実装してみる
タグと挙動は多分同じだけど
ログインユーザーごとにいいねされた記録を作らないなら、id, board_id, countくらいで良さそうだ
docker-compose exec web bundle exec rails g model good board:references count:integer
docker-compose exec web bundle exec rails g model board_good_relation board:references good:references
というか掲示板ごとのいいねなら、boardsにgoodを足せばいいじゃん
rails generate migration AddColumnBoards
code:rb
docker-compose exec web rails generate migration AddColumnBoards
Running via Spring preloader in process 212
invoke active_record
create db/migrate/20220201101946_add_column_boards.rb
code:Rails/db/migrate/20220201101946_add_column_boards.rb
class AddColumnBoards < ActiveRecord::Migration5.0 def change
add_column(:boards, :good, :integer, null:false)
end
end
docker-compose exec web bundle exec rake db:migrate
やってみたけどデフォルト0にするの忘れてたからロールバックしたい
バージョン確認
code:rb
% docker-compose exec web rails db:version
Current version: 20220201101946
これはmigrationファイルの数字が記載されるので、1つ戻りたいならロールバック
code:rb
docker-compose exec web rails db:rollback
== 20220201101946 AddColumnBoards: reverting ==================================
-- remove_column(:boards, :good, :integer, {:null=>false})
-> 0.0431s
== 20220201101946 AddColumnBoards: reverted (0.0520s) =========================
Annotated (3): app/models/board.rb, test/models/board_test.rb, test/fixtures/boards.yml
% docker-compose exec web rails db:version
Current version: 20220125110045
もっかいやったのでカラムが増えてるか確認したい
みてるページでdocker attach rails_web_1 ->binding.pry->操作後exit->ctrl + p -> Q
code:pry
# goodカラムが増えていることが確認できる
4 pry(#<BoardsController>)> Board.find(1) Board Load (0.5ms)
SELECT
boards. *
FROM
boards
WHERE
boards.id = 1 LIMIT 1
id: 1,
name: "名前です。ア",
title: "タイトルです",
body: "保存済みです。\r\nアップデートしました。\r\nパーシャルでアップデートしました。\r\n",
created_at: Thu, 13 Jan 2022 20:38:26 JST +09:00,
updated_at: Tue, 25 Jan 2022 19:51:00 JST +09:00,
good: 0> ⬅️初期値0
5 pry(#<BoardsController>)> Board => Board(id: integer, name: string, title: string, body: text, created_at: datetime, updated_at: datetime, good: integer) ⬅️ある
詳細画面でgoodを増やすボタンを作るぞ
URLから対象のviewを探してみる
evernoteから引用
デバッグ用の関数たち
pp @book
railsのコンソール上に表示される。
<%= debug @book %>
view側で確認したい場合はこれ。
<%= @book %>
インスタンスだと確認すること困難だが、配列とか文字列ならこれで表示すれば中身が見える
該当ページの探し方
routeを見る
get "/books/:id/edit", to: "books#edit", as: "edit_book" だと、
get URL, to: コントローラ名#メソッド名, ルーティングの名前がわかるのでここから参照する
今回はboards/1のviewが知りたい。
ルートはresources :boards, only: [:index, :new, :create, :show, :edit, :update, :destroy]
/boards/:id(.:format) boards#showと書かれているので、boards_controllerのshowに処理が書かれる
viewはコントローラに合わせて作られるので、Rails/app/views/boards/show.html.erbを見れば良い
boards/1にボタン作ったのでformで囲む
<%= f.submit '💕 x ' << board.good.to_s , class: 'btn btn-success btn-sm' %>
to_sメソッドでstringに直して繋ぐ
いいねボタン作った boardの値使う時は<< で文字列連結
submitした先、どのコントローラの記述で処理を書く?
なんとなく分かったかも
form_for @boardで、show.html.erbで書いているならboards_controller.showメソッドに書く
laravelとは違って、viewファイルの名前そのものが処理を動かすメソッド名になっている
showにはコメントの処理が既に書かれているけど、そこにいいねの処理も追加する
コメントの処理はコメントのformの情報を持っていないので、スルーされる
コメント投稿機能はどうなってる?
http://localhost:3000/boards/1にあるコメント機能
boards_controller.showで@comment = Comment.new(board_id: @board.id)という記述がある
これは@commentオブジェクトを作って、viewに渡している(講座66)
comments_controller.createでbinding_pryを使ってみてみると、
code:create
comment => {
"board_id" => 1,
"name" => "aaa",
"comment" => "はじめまして",
}
というような値が確認できる
わからないところ
いいねボタンはboards.showのビューで送ったから、boards_controller.showメソッドで処理しているのに、どうしてコメントはboards.showからcomments.createで処理が行われている?
commentのedit, update機能を実装して自分で確認してみる。
edit
code:rb
# viewに追加
<span><%= link_to '編集', comment, method: :edit %></span>
# route追加
エラーはく
https://scrapbox.io/files/62010ecb4a0936001d3579e5.png
/comments/2/editに飛ばしたい。/rails/info/routesを見てみる
https://scrapbox.io/files/62010f2a7027560022fec140.png
comments#editにのHelper欄を見ると、edit_comment_pathという表記がある!
これをlink_toに入れてあげればよかった。 が、まだエラー
https://scrapbox.io/files/6201105cc2d49c001d5702ca.png
POSTなのがいけないっぽい。
<span><%= link_to '編集', edit_comment_path %></span>でいけた。
method指定をなくすと getで送られるらしい。
method:っていうのは:editとかじゃなくて、HTTPメソッドのこと。
commentの宛先が全部comments/1になってる
<span><%= link_to '編集', comment %></span>ならid取れるけど/editにならない...
span><%= link_to '編集', edit_comment_path(id: comment.id) %></span>でできた!
()でコントローラへと送るパラメータを指定してあげればよかった。
コントローラではparams[:id]で確認できる
viewで受け取る。コントローラ でインスタンス変数(@...)を作って渡してやれば良い
でも、boards#editはcontrollerに記述ひとつもないのに渡されてる....
まあいいか
editのview内容
code:erb
<%= form_for(@comment) do |f| %>
<div class="form-group">
<%= f.label :title, '名前' %>
<%= f.text_field :name, class: 'form-control' %>
<div>
<div class="form-group">
<%= f.label :title, 'コメント内容' %>
<%= f.text_field :comment, class: 'form-control' %>
</div>
<%= f.submit '編集を終える', class: 'btn btn-primary' %>
<% end %>
formをHTMLに直すと以下のような形になる。
code:html
<form class="edit_comment" id="edit_comment_2" action="/comments/2" accept-charset="UTF-8" method="post">
update作ろう
routesにパス追加
controllerにupdateを追加
いろいろ書いて設計できた。何か悩んだらこの部分見るといいかも。
sessionとcookie
code:comments_controller.rb
def update
@comment = Comment.find(params:id) is_update = @comment.update(comment_params)
if is_update == true
@board = Board.find(@comment.board_id)
cookies.delete :test_cookie # cookieの削除
redirect_to @board
else
redirect_to :back, flash: {
comment: @comment,
error_messages: @comment.errors.full_messages,
flash_name: 'flash_nameです',
flash_param: params
}
end
end
こんな感じで書いてみた。配置の仕方と削除の仕方がメイン
commentsのupdateにバリデートがかかったら動作するので確認してみよう
HTTPでのユーザー認証の仕組み
ステートレス
現在の状態を表すデータなどを保持せず、入力内容のみで出力が決まる
2回目以降は誰か判別できない
セッションを使ってユーザー情報を保管しておくことで実装する
実際に作成する
PWなど暗号化に使うgemのインストール
# gem 'bcrypt', '~> 3.1.7'が最初からGemfileにコメントとして入っているのでいれる docker-compose exec web bundle
ユーザーModelを作成する
docker-compose exec web rails g model user name:string password_digest:string
docker-compose exec web \ rails g model user name:string password_digest:string
ちなみに上のように、バックスラッシュ入れると複数行でターミナルに入れられる
code:terminal
% docker-compose exec web \
rails g model user name:string password_digest:string
Running via Spring preloader in process 64
invoke active_record
create db/migrate/20220223110553_create_users.rb
create app/models/user.rb
invoke test_unit
create test/models/user_test.rb
create test/fixtures/users.yml
password_digestはgemで使う名前の形式で名付けているので、このgemで管理する時は名前は変更しないこと
migrateファイル編集
ユニーク制約とnull非許容制約をつける
add_index :users, :name, unique: trueはどういう意味?
ページにインデックスを付与するメソッド
インデックスを付与すると、ここからデータを呼び出す場合に動作が早くなるのだ
migrate
docker-compose exec web rails db:migrate
セッション情報を操作するためのコントローラを作成する
docker-compose exec web rails g controller sessions create destroy --skip-template-engine
↑の意味
rails g controller コントローラ名 作成アクション名1 2 3 ...
コントローラ名は多分複数形がいい(boards, sessions, users...)
createアクションで、ログイン成功時にPWに準したsession idを付与する
destroyでセッション情報からユーザー情報を削除する(ログアウト)
--skip-template-engine : viewファイルとかを併せて作らなくても良いときのオプション
ちなみにtypoでコントローラを生成してしまった場合は以下で消せる
docker-compose exec web rails destroy controller sessions create destroy
併せて作られたviewとかも全部消してくれるよ
ついでにHomeControllerを作ろう
https://scrapbox.io/files/621618bedc1ebb001e29d451.png
docker-compose exec web rails g controller home index
作った後はエラーページにならないはず
ユーザーを操作するコントローラも作っておこう
docker-compose exec web rails g controller users new create me
new : ユーザー作成フォーム
create : フォーム情報を基にユーザを作成するアクション
me : ログイン中ユーザのマイページを操作するアクション
createのviewファイルは不要なので消しておこう
rm app/views/users/create.html.erb